package com.xgk.boot.framework.security.core.filter;

import cn.hutool.core.util.StrUtil;
import com.xgk.boot.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import com.xgk.boot.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.xgk.boot.framework.common.constant.ErrorCodeConstants;
import com.xgk.boot.framework.common.constant.TokenConstants;
import com.xgk.boot.framework.common.context.TenantContextHolder;
import com.xgk.boot.framework.common.exception.ServiceException;
import com.xgk.boot.framework.common.pojo.CommonResult;
import com.xgk.boot.framework.common.util.jwt.JwtUtil;
import com.xgk.boot.framework.common.util.servlet.ServletUtils;
import com.xgk.boot.framework.security.config.SecurityProperties;
import com.xgk.boot.framework.security.core.LoginUser;
import com.xgk.boot.framework.security.core.util.SecurityFrameworkUtils;
import com.xgk.boot.framework.web.core.handler.GlobalExceptionHandler;
import com.xgk.boot.framework.web.core.util.WebFrameworkUtils;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

/**
 * Token 过滤器，验证 token 的有效性
 * 验证通过后，获得 {@link LoginUser} 信息，并加入到 Spring Security 上下文
 *
 * @author xgk
 */
@Slf4j
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private final SecurityProperties securityProperties;

    private final GlobalExceptionHandler globalExceptionHandler;

    private final OAuth2TokenCommonApi oauth2TokenApi;


    @Override
    @SuppressWarnings("NullableProblems")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        try {
            authCheck(request, response);
        } catch (Throwable ex) {
            log.error("parse user error",ex);
            CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
            ServletUtils.writeJSON(response, result);
            return;
        }
        chain.doFilter(request, response);
    }

    /**
     * 登录用户检查
     *
     * @param request
     * @param response
     */
    private void authCheck(HttpServletRequest request, HttpServletResponse response) {

            String requestURI = request.getRequestURI();
            List<String> permitAllUrls = securityProperties.getPermitAllUrls();
            PathMatcher pathMatcher = new AntPathMatcher();
            for (String permitAllUrl : permitAllUrls) {
                if (pathMatcher.match(permitAllUrl, requestURI)) {
                    return;
                }
            }
            //bearer token
            String token = SecurityFrameworkUtils.obtainAuthorization(request,
                    securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
            if (StrUtil.isNotEmpty(token)) {

                LoginUser loginUser = buildLoginUserByToken(token, SecurityFrameworkUtils.AUTHORIZATION_BEARER);
                if (loginUser != null) {
                    SecurityFrameworkUtils.setLoginUser(loginUser, request);
                }

            } else {
                //web自动刷新token
                try {
                    token = SecurityFrameworkUtils.obtainCookieAuth(request, TokenConstants.ELM_TOKEN_NAME);
                    if (StrUtil.isNotEmpty(token)) {
                        LoginUser loginUser = buildLoginUserByToken(token, null);
                        if (loginUser != null) {
                            loginUser.setTokenAutoRefresh(true);
                            loginUser.setToken(token);
                            SecurityFrameworkUtils.setLoginUser(loginUser, request);
                            refreshToken(loginUser,requestURI,response);
                        }
                    }
                }catch (ServiceException e) {
                    if(e.getCode().equals(ErrorCodeConstants.UNAUTHORIZED_EXPIRE.getCode())) {
                        SecurityFrameworkUtils.setElmExprieCookie(response);
                    }
                    throw e;
                }
            }
    }

    private LoginUser buildLoginUserByBearerToken(String token) {

        OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);
        if (accessToken == null) {
            return null;
        }
        // 用户类型不匹配，无权限
        // 注意：只有 /admin-api/* 和 /app-api/* 有 userType，才需要比对用户类型
        // 类似 WebSocket 的 /ws/* 连接地址，是不需要比对用户类型的
        // 构建登录用户
        return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
                .setInfo(accessToken.getUserInfo()) // 额外的用户信息
                .setUsername(accessToken.getUserName())
                .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
                .setExpiresTime(accessToken.getExpiresTime());
    }

    private LoginUser buildLoginUserByToken(String token, String tokenType) {

        if (StrUtil.isNotBlank(tokenType) && tokenType.equals(SecurityFrameworkUtils.AUTHORIZATION_BEARER)) {
            return buildLoginUserByBearerToken(token);
        }
        OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkWebAccessToken(token);
        if (accessToken == null) {
            return null;
        }
        TenantContextHolder.setTenantId(accessToken.getTenantId());
        // 构建登录用户
        return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
                .setInfo(accessToken.getUserInfo()) // 额外的用户信息
                .setUsername(accessToken.getUserName())
                .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
                .setExpiresTime(accessToken.getExpiresTime());
    }

    /**
     * 模拟登录用户，方便日常开发调试
     * <p>
     * 注意，在线上环境下，一定要关闭该功能！！！
     *
     * @param request  请求
     * @param token    模拟的 token，格式为 {@link SecurityProperties#getMockSecret()} + 用户编号
     * @param userType 用户类型
     * @return 模拟的 LoginUser
     */
    private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {
        if (!securityProperties.getMockEnable()) {
            return null;
        }
        // 必须以 mockSecret 开头
        if (!token.startsWith(securityProperties.getMockSecret())) {
            return null;
        }
        // 构建模拟用户
        Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
        return new LoginUser().setId(userId).setUserType(userType)
                .setTenantId(WebFrameworkUtils.getTenantId(request));
    }

    private void refreshToken(LoginUser loginUser,String url, HttpServletResponse response){
        List<String> noRefeshTokenUrls = securityProperties.getNoRefeshTokenUrls();
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String permitAllUrl : noRefeshTokenUrls) {
            if (pathMatcher.match(permitAllUrl, url)) {
                loginUser.setTokenAutoRefresh(false);
                return;
            }
        }

        String token = loginUser.getToken();
        if (!JwtUtil.isTokenNearExpiration(token)) {
            return;
        }
        log.info("check token refresh");
        String jwtToken = JwtUtil.renewalJwt(token);
        SecurityFrameworkUtils.addTokenCookie(jwtToken, response);
    }


}
